#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/poll.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <asm/uaccess.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
#include <linux/sched.h>
#include <linux/gpio.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>

/*
 * interrupt
 */

#define DEVICE_NAME "key_init"
#define PIN_UP 1

static int key_major = 191;

struct button_irq_desc
{
    int irq;
    int pin;
    int pin_setting;
    int number;
    char * name;
};

static struct button_irq_desc button_irqs[] = {
    {IRQ_EINT8, GPG(0), GPGO_EINT8, 0, "KEY0"},
    {IRQ_EINT9, GPG(1), GPGO_EINT11, 1, "KEY1"},
    {IRQ_EINT10, GPG(2), GPGO_EINT12, 2, "KEY2"},
    {IRQ_EINT11, GPG(3), GPGO_EINT13, 3, "KEY3"},
    {IRQ_EINT12, GPG(4), GPGO_EINT14,4, "KEY4"},
    {IRQ_EINT13, GPG(5), GPGO_EINT15, 5, "KEY5"},
};

static volatile char key_values[6] = {0, 0, 0, 0, 0, 0};
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
static volatile int ev_press = 0;

static irqreturn_t buttons_interrupt(int irq, void * dev_id)
{
    struct button_irq_desc * button_irqs = (struct button_irq_desc *) dev_id;
    int down;

    down = gpio_getpin(button_irqs->pin);
    if (!down) {
        key_values[button_irqs->number] = PIN_UP;
        // ev_press = 1;
        // wake_up_interruptible(&button_waitq);
        // wait_event_interruptible(button_waitq, ev_press);
        wake_up_interruptible(&button_waitq);
    }

    return IRQ_RETVAL(IRQ_HANDLED);
}

static int buttons_open(struct inode * inode, struct file * file)
{
    int i;
    int err = 0;

    for (i = 0; i < sizeof(button_irqs) / sizeof(button_irqs[0]); i++) {
        if (button_irqs[i].irq < 0)
            continue;

        err = request_irq(button_irqs[i].irq, buttons_interrupt, IRQ_TYPE_EDGE_FALLING,
                          button_irqs[i].name, (void *) &button_irqs[i]);
        if (err)
            break;
    }

    if (err) {
        i--;
        for (; i >= 0; i--) {
            if (button_irqs[i].irq < 0)
                continue;

            disable_irq(button_irqs[i].irq);
            free_irq(button_irqs[i].irq, (void *) &button_irqs[i]);
        }

        return -EBUSY;
    }

    ev_press = 1;
 
    return 0;
}

static int buttons_close(struct inode * inode, struct file * file)
{
    int i;

    for (i = 0; i < sizeof(button_irqs) / sizeof(button_irqs[0]); i++) {
        if (button_irqs[0].irq < 0)
            continue;

        free_irq(button_irqs[0].irq, (void *) &button_irqs[i]);
    }

    return 0;
}

static int buttons_read(struct file * filp, char __user * buff, 
                        size_t count, loff_t * offp)
{
    unsigned long err;
    unsigned int numb;

    wait_event_interruptible(button_waitq, ev_press);
    ev_press = 0;

    err = copy_to_user(buff, (const void *) key_values,
                       min(sizeof(key_values), count));

    for (numb = 0; numb <= 5; numb++)
        key_values[numb] = 0;

    return err ? -EFAULT:min(sizeof(key_values), count);
}

static struct file_operations dev_fops = {
    .owner = THIS_MODULE,
    .open = buttons_open,
    .release = buttons_close,
    .read = buttons_read,
};

static int __init dev_init(void)
{
    int ret;

    ret = register_chrdev(key_major, DEVICE_NAME, &dev_fops);
    printk("initialized.\n");

    return ret;
}

static void __exit dev_exit(void)
{
    unregister_chrdev(key_major, DEVICE_NAME);
}

module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("GPL");


